home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
The Fatted Calf
/
The Fatted Calf.iso
/
Applications
/
Graphics
/
nxyplot
/
Source
/
Plot.m
< prev
next >
Wrap
Text File
|
1994-02-01
|
68KB
|
2,325 lines
/* Generated by Interface Builder */
#import "defs.h"
#import "Plot.h"
#import <appkit/appkit.h>
#import <objc/Storage.h>
#import <math.h> /* for MAXFLOAT, etc. */
#import <strings.h>
#import <streams/streams.h>
#import "ColumnSelectionHandler.h"
#import "ErrorBarHandler.h"
#import <defaults/defaults.h>
/* The following routines are in auxil.m: */
extern void computeNiceLinInc(float *, float *, float *);
extern void computeNiceLogInc(float *, float *, float *);
@implementation Plot
- makeSomeScrollWindows
{
// the following lines exist only to initialize the ScrollWindows.....pdhowell
NXSize minsize = {0.0,0.0};
[lineMatrixWindow becomeScrollWindow];
[symbolMatrixWindow becomeScrollWindow];
[legendFormWindow becomeScrollWindow];
[lineMatrixWindow setMinFrameSize:minsize];
[symbolMatrixWindow setMinFrameSize:minsize];
[legendFormWindow setMinFrameSize:minsize];
return self;
}
+ initialize
{
const NXDefaultsVector nxyplotDefaults = {
{ "colorOption", "NO"},
{ "colorPrinting", "NO"},
{ "cycleLineStyles", "NO"},
{ "opaqueBackground", "YES"},
{ "autoPlot", "YES"},
{ NULL, NULL}
};
self = [[Object alloc] init];
NXRegisterDefaults("nxyplot", nxyplotDefaults);
return self;
}
- init
{
// Initialize variables here:
nfilestotal = 0;
ncurvestotal = 0;
globaldatamin.x = MAXFLOAT;
globaldatamin.y = MAXFLOAT;
globaldatamax.x = -MAXFLOAT;
globaldatamax.y = -MAXFLOAT;
beepError = 0;
backgroundcolor = NX_COLORWHITE;
textcolor = NX_COLORBLACK;
srandom(10); /* initialize for color selection */
oldMin.x = 0.0;
oldMin.y = 0.0;
oldMax.x = 0.0;
oldMax.y = 0.0;
oldInc.x = 0.0;
oldInc.y = 0.0;
currentMin.x = 0.0;
currentMin.y = 0.0;
currentMax.x = 0.0;
currentMax.y = 0.0;
currentInc.x = 0.0;
currentInc.y = 0.0;
return self;
}
// Delete all data (free up the space that was malloc'ed)
- removeAllFiles:sender
{
int n, j;
datahunk *pdh;
const char * generictitle = "NXYPLOT";
if (nfilestotal == 0) {
return self;
}
// Put up an alert panel. This method is called by a menu item and also
// by the removeAndOpen method; only if it's called by the menu item
// do we want to put up the alert panel.
if (sender != self) {
if (NXRunAlertPanel("Remove all", "Remove all files and clear plot",
"OK", "Cancel", NULL) == NX_ALERTALTERNATE) {
return self;
}
}
for (n=nfilestotal-1; n>=0; n--) {
pdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
for (j = 0; j < pdh->ncurves; j++) {
free( (void *)*(pdh->y+j) );
}
free( (void *)(pdh->y) );
if (pdh->has_eybars) {
for (j = 0; j < pdh->ncurves; j++) {
free( (void *)*(pdh->ey+j) );
}
free( (void *)(pdh->ey));
}
free( (void *)(pdh->x) );
if (pdh->has_exbars) {
free( (void *)(pdh->ex));
}
free( (void *)(pdh->filename) );
}
[datahunkArray empty];
[self adjustPanels:ncurvestotal :-1]; /* -1 is a special signal */
nfilestotal = 0;
[columnSelectionHandler removeAll:self];
[errorBarHandler removeAll:self];
[canvas display]; /* clear the canvas */
[[canvas window] setTitle:generictitle];
ncurvestotal = 0;
// reset globaldatamin/max
globaldatamin.x = MAXFLOAT;
globaldatamin.y = MAXFLOAT;
globaldatamax.x = -MAXFLOAT;
globaldatamax.y = -MAXFLOAT;
// clear xMin/Max/Inc and yMin/Max/Inc windows:
[xLimits setStringValue:"" at:0];
[xLimits setStringValue:"" at:1];
[xLimits setStringValue:"" at:2];
[yLimits setStringValue:"" at:0];
[yLimits setStringValue:"" at:1];
[yLimits setStringValue:"" at:2];
srandom(10); /* initialize for color selection */
oldMin.x = 0.0;
oldMin.y = 0.0;
oldMax.x = 0.0;
oldMax.y = 0.0;
oldInc.x = 0.0;
oldInc.y = 0.0;
currentMin.x = 0.0;
currentMin.y = 0.0;
currentMax.x = 0.0;
currentMax.y = 0.0;
currentInc.x = 0.0;
currentInc.y = 0.0;
return self;
}
// Remove all existing files and open a new one
- removeAndOpen:sender
{
if (NXRunAlertPanel("New",
"Remove all files, clear plot\nand open new file",
"OK", "Cancel", NULL) == NX_ALERTALTERNATE) {
return self;
}
[self removeAllFiles:self];
[self open:self];
return self;
}
- fixFileRemovalPanel:sender
{
int n;
char title[80];
NXCoord dy;
int numrows, numcols;
NXSize cellsize, intercell;
// Fix up the filename matrix
[fileRemovalMatrix getNumRows:&numrows numCols:&numcols];
[fileRemovalMatrix getCellSize:&cellsize];
[fileRemovalMatrix getIntercell:&intercell];
[fileRemovalMatrix renewRows:nfilestotal cols:1];
dy = (NXCoord)(nfilestotal - numrows) * (cellsize.height + intercell.height);
for (n=0; n<nfilestotal; n++) {
if (!strncmp([self filename:(unsigned)n], "pasteboard", 10))
sprintf(title, "pasteboard");
else
sprintf(title, strrchr([self filename:(unsigned)n], '/') + 1);
[[fileRemovalMatrix cellAt:n :0] setStringValue:title];
}
[fileRemovalMatrix sizeToCells];
[fileRemovalMatrix moveBy:0.0 :-dy];
// Fix up the buttons that go with the matrix of names
[fileRemovalButtons getCellSize:&cellsize];
[fileRemovalButtons getIntercell:&intercell];
dy = (NXCoord)(nfilestotal - numrows) * (cellsize.height + intercell.height);
[fileRemovalButtons renewRows:nfilestotal cols:1];
[fileRemovalButtons sizeToCells];
[fileRemovalButtons moveBy:0.0 :-dy];
for (n=0; n<nfilestotal; n++) {
[ [fileRemovalButtons cellAt:n :0] setState:0]; /* a safety play */
}
[fileRemovalPanel display];
if (sender != self) {
/* If this method is called from the menu, make the window key. */
[fileRemovalPanel makeKeyAndOrderFront:self];
}
return self;
}
// Remove some existing files; the file removal is not hard, but correctly
// updating the linestyle, symbolstyle, and legend matrices is harder.
// Correctly handling the ColumnSelection and ErrorBar panels is even harder.
- removeSomeFiles:sender
{
int n, i, j;
int old_index = 0, current_index = 0;
/* These integers point to the columns of the linestyle and symbolstyle
* matrices as we run along updating the matrices. They also serve in
* updating the curvecolors array.
*/
datahunk *pdh;
int *newlinestyles, *newsymbolstyles;
if (nfilestotal == 0) {
return self;
}
/*
* If all files are marked for deletion, we will call the "removeAllFiles"
* method. So check here to see if all files are marked for deletion.
*/
i = 1;
for (n=0; n < nfilestotal; n++) {
if ([[fileRemovalButtons cellAt:n :0] state] == 0) {
i = 0; // there is a file not being deleted
break;
}
}
if (i == 1) { // all files are to be deleted
[self removeAllFiles:self];
return self;
}
/* Figure out what the linestyle and symbolstyle matrices should look
* like after the file removal process. We do it this way because simply
* copying the columns of the matrices fails after the renewRows:cols:
* message is sent to them.
*/
newlinestyles = (int *)malloc(ncurvestotal * sizeof(int));
newsymbolstyles = (int *)malloc(ncurvestotal * sizeof(int));
for (n=0; n < nfilestotal; n++) {
if ([[fileRemovalButtons cellAt:n :0] state] == 1) {
/* Just bump the index if the file is to be deleted. */
old_index += [self nCurves:n];
}
else {
for (i=0; i < [self nCurves:n]; i++) {
for (j=0; j < N_LINE_STYLES; j++) {
if ([ [lineMatrix cellAt:j :old_index] state] == 1) {
newlinestyles[current_index] = j;
break;
}
}
for (j=0; j < N_SYMBOL_STYLES; j++) {
if ([ [symbolMatrix cellAt:j :old_index] state] == 1) {
newsymbolstyles[current_index] = j;
break;
}
}
/* Update the curvecolors array: */
curvecolors[current_index] = curvecolors[old_index];
old_index++;
current_index++;
}
}
}
/* We could do a realloc on curvecolors here, but not much would be saved. */
/* Reset the running indices. */
old_index = 0;
current_index = 0;
for (n=0; n < nfilestotal; n++) {
/* Is the nth file marked for deletion? */
if ([[fileRemovalButtons cellAt:n :0] state] == 1) { /* yes it is */
pdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
for (j = 0; j < pdh->ncurves; j++) {
free( (void *)*(pdh->y+j) );
}
free( (void *)(pdh->y) );
if (pdh->has_eybars) {
for (j = 0; j < pdh->ncurves; j++) {
free( (void *)*(pdh->ey+j) );
}
free( (void *)(pdh->ey));
}
free( (void *)(pdh->x) );
if (pdh->has_exbars) {
free( (void *)(pdh->ex));
}
free( (void *)(pdh->filename) );
old_index += [self nCurves:n];
}
else {
/* Column copying of the linestyle and symbolstyle matrices
* is handled later. Do the legend form here.
*/
for (j=0; j < [self nCurves:n]; j++) {
[legendForm setStringValue:[legendForm stringValueAt:old_index]
at:current_index];
[legendForm drawCellAt:current_index];
old_index++;
current_index++;
}
}
}
/* Get rid of extraneous legendForm entries: */
for (j=current_index; j<ncurvestotal; j++) {
[legendForm removeEntryAt:j];
}
[legendForm sizeToFit];
[ [legendForm window] display];
/*
* Do the ColumnSelectionHandler manipulation here, before nfilestotal
* gets reset.
*/
[columnSelectionHandler update:self];
[errorBarHandler update:self];
/* We put the datahunkArray manipulation here because the removeAt method
* shifts the elements of the datahunkArray to close the gap created by
* removing one element; for this reason we count down rather than up.
*/
j = nfilestotal;
for (n=nfilestotal-1; n >= 0; n--) {
if ([[fileRemovalButtons cellAt:n :0] state] == 1) {
[datahunkArray removeElementAt:(unsigned)n];
j--;
}
}
nfilestotal = j;
ncurvestotal = current_index;
/* Now resize and display the linestyle and symbolstyle matrices */
[lineMatrix renewRows:N_LINE_STYLES cols:ncurvestotal];
[lineMatrix sizeToCells];
for (i=0; i<ncurvestotal; i++) {
for (j=0; j<N_LINE_STYLES; j++) {
[ [lineMatrix cellAt:j :i] setState:0];
}
[ [lineMatrix cellAt:newlinestyles[i] :i] setState:1];
}
[lineText renewRows:1 cols:ncurvestotal];
[ [lineMatrix window] display];
[symbolMatrix renewRows:N_SYMBOL_STYLES cols:ncurvestotal];
[symbolMatrix sizeToCells];
for (i=0; i<ncurvestotal; i++) {
for (j=0; j<N_SYMBOL_STYLES; j++) {
[ [symbolMatrix cellAt:j :i] setState:0];
}
[ [symbolMatrix cellAt:newsymbolstyles[i] :i] setState:1];
}
[symbolText renewRows:1 cols:ncurvestotal];
[ [symbolMatrix window] display];
[self adjustScrollWindows];
/* must also fix up appearence of the file removal window */
[self fixFileRemovalPanel:self];
[fileRemovalPanel performClose:self];
free((void *)newlinestyles);
free((void *)newsymbolstyles);
/* and replot automatically */
if (nfilestotal == 0) {
[canvas display];
}
else {
[self drawPlot:self];
}
return self;
}
/* return the x data for the nth file */
- (NXCoord *)xdata:(int)n
{
datahunk *pdh;
pdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
return pdh->x;
}
/* return the y data for the nth file */
- (NXCoord **)ydata:(int)n
{
datahunk *pdh;
pdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
return pdh->y;
}
/* return the y error data for the nth file */
- (NXCoord **)eydata:(int)n
{
datahunk *pdh;
pdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
return pdh->ey;
}
/* return the x error data for the nth file */
- (NXCoord *)exdata:(int)n
{
datahunk *pdh;
pdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
return pdh->ex;
}
/* return the number of x-points in the nth file */
- (int)nPoints:(int)n
{
datahunk *pdh;
pdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
return pdh->npoints;
}
/* return the number of curves in the nth file */
- (int)nCurves:(int)n
{
datahunk *pdh;
pdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
return pdh->ncurves;
}
/* return the name of the nth file */
- (char *)filename:(unsigned)n
{
datahunk *pdh;
pdh = (datahunk *)[datahunkArray elementAt:n];
return pdh->filename;
}
/* Does the nth file have error bars in y? */
- (BOOL) has_eybars:(int)n
{
datahunk *pdh;
pdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
return pdh->has_eybars;
}
/* Does the nth file have error bars in x? */
- (BOOL) has_exbars:(int)n
{
datahunk *pdh;
pdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
return pdh->has_exbars;
}
- (int)nCurvesTotal { return ncurvestotal;}
- (int)nFiles { return nfilestotal;}
- makeLineStyle:(int)aCurve :(int)lineStyle
{
int row;
for (row = 0; row < N_LINE_STYLES; row++)
[[lineMatrix cellAt:row :aCurve] setState:0]; /* turn off all */
[[lineMatrix cellAt:lineStyle :aCurve] setState:1];
return self;
}
- makeSymbolType:(int)aCurve :(int)symType;
{
int row;
for (row = 0; row < N_SYMBOL_STYLES; row++)
[[symbolMatrix cellAt:row :aCurve] setState:0]; /* turn off all */
[[symbolMatrix cellAt:symType :aCurve] setState:1];
return self;
}
- (BOOL) xaxisLog
{
if ( [xLinLog state] ) return YES;
else return NO;
}
- forceXaxisLinear
{
[xLinLog setState:0];
[xLinLog display];
return self;
}
- forceXaxisLog
{
[xLinLog setState:1];
[xLinLog display];
return self;
}
- (BOOL) yaxisLog
{
if ( [yLinLog state] ) return YES;
else return NO;
}
- forceYaxisLinear
{
[yLinLog setState:0];
[yLinLog display];
return self;
}
- forceYaxisLog
{
[yLinLog setState:1];
[yLinLog display];
return self;
}
- (BOOL) shouldChangeLegendFont
{
if ( [changeLegendFont state] ) return YES;
else return NO;
}
- (BOOL) shouldChangeLegendTitleFont
{
if ( [changeLegendTitleFont state] ) return YES;
else return NO;
}
- (BOOL) shouldChangeMainTitleFont
{
if ( [changeMainTitleFont state] ) return YES;
else return NO;
}
- (BOOL) shouldChangeYTitleFont
{
if ( [changeYTitleFont state] ) return YES;
else return NO;
}
- (BOOL) shouldChangeXTitleFont
{
if ( [changeXTitleFont state] ) return YES;
else return NO;
}
- (BOOL) shouldChangeTicLabelFont
{
if ( [changeTicLabelFont state] ) return YES;
else return NO;
}
- (int)providelinestyle:(int)aCurve
{
int row, cellstate;
/* First, if line is turned off, return that */
if ([ [lineMatrix cellAt:N_LINE_STYLES-1 :aCurve] state] == 1) {
return N_LINE_STYLES-1;
}
/*
* Next, check if we are printing or previewing and if we should
* cycle the line styles
*/
if ( ( ([printPreview state] == 1) || (NXDrawingStatus == NX_PRINTING))
&& ([accPrintLineStyleButton state] == 1) ) {
return aCurve % (N_LINE_STYLES - 1);
}
/*
* Here we can just look at the linestyle matrix.
*/
else {
for (row = 0; row < N_LINE_STYLES; row++) {
cellstate = [ [lineMatrix cellAt:row :aCurve] state];
if (cellstate == 1) return row;
}
}
return 0; /* for safety */
}
- (int)providesymbolstyle:(int)aCurve
{
int row, cellstate;
for (row = 0; row < N_SYMBOL_STYLES; row++) {
cellstate = [ [symbolMatrix cellAt:row :aCurve] state];
if (cellstate == 1) return row;
}
return 0; /* for safety */
}
/*
* Changed floats to doubles in the following group. With floats, when
* the value was 250.4, the value returned by floatValueAt: was
* 250.399994 (for example). (This appears to be a problem with the
* atof function on Unix 32-bit systems.) This gave troubles in the
* tic mark routine in PlotView.m.
*/
- (double)provideXmin {return [xLimits doubleValueAt:0];}
- (double)provideXmax {return [xLimits doubleValueAt:1];}
- (double)provideXinc {return [xLimits doubleValueAt:2];}
- (double)provideYmin {return [yLimits doubleValueAt:0];}
- (double)provideYmax {return [yLimits doubleValueAt:1];}
- (double)provideYinc {return [yLimits doubleValueAt:2];}
- resetXmin:(double)aNum { [xLimits setDoubleValue:aNum at:0]; return self; }
- resetXmax:(double)aNum { [xLimits setDoubleValue:aNum at:1]; return self; }
- resetXinc:(double)aNum { [xLimits setDoubleValue:aNum at:2]; return self; }
- resetYmin:(double)aNum { [yLimits setDoubleValue:aNum at:0]; return self; }
- resetYmax:(double)aNum { [yLimits setDoubleValue:aNum at:1]; return self; }
- resetYinc:(double)aNum { [yLimits setDoubleValue:aNum at:2]; return self; }
- (float)provideGlobalXmin {return globaldatamin.x;}
- (float)provideGlobalYmin {return globaldatamin.y;}
- resetMinMax:sender
{
int n;
datahunk * pdh;
// We have to go through all the data and recalculate min/max since
// some data curves may have been "turned off" (by setting their linestyle
// to none and symbolstyle to none).
for (n=0; n<nfilestotal; n++) {
pdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
[self findMinMax:pdh];
}
[self findGlobalMinMax];
[self niceMinMaxInc];
[self drawPlot:self]; /* redraw plot automatically */
return self;
}
- drawPlotButton:(int)state
{
if (state==0) {
[plotButton highlight:NO]; /* will display normal title */
}
if (state==1) {
[plotButton highlight:YES]; /* will display alternate title */
}
return self;
}
// We make the plotParam object responsible for checking parameters
// before the PlotView object is called. Thus the PlotView object can
// assume the parameters are OK, and it doesn't have to do any checking.
// Things to check: xinc has the same sign as xmax-xmin (same for y);
// no negative data if log plot requested on x or y axis; there won't be
// too many tic marks requested.
- sanityCheck
{
float xinc = [self provideXinc];
float xmax = [self provideXmax], xmin = [self provideXmin];
float yinc = [self provideYinc];
float ymax = [self provideYmax], ymin = [self provideYmin];
int nticmarks;
/* First check: no nonpositive data if logarithmic axis */
/* Also check that increment is > 5 (need rint(log10(increment)) >=1 ) */
if ( [self xaxisLog] ) {
if (globaldatamin.x <= 0.0 || xmin <= 0.0 || xmax <= 0.0) {
[xLinLog setState:0]; /* back to linear */
NXBeep(); /* audible alert */
beepError = 1;
}
if (xinc < 5.0) {
[self resetXinc:(double)10.0];
NXBeep();
beepError = 12;
}
}
if ( [self yaxisLog] ) {
if (globaldatamin.y <= 0.0 || ymin <= 0.0 || ymax <= 0.0) {
[yLinLog setState:0]; /* back to linear */
NXBeep(); /* audible alert */
beepError = 2;
}
if (yinc < 5.0) {
[self resetYinc:(double)10.0];
NXBeep();
beepError = 12;
}
}
/* Second check: xinc has same sign as xmax and xmin */
if (xinc*(xmax-xmin) <= 0.0) { /* the bad case - avoid infinite loop */
if (xinc < 0.0) { /* in PlotView:drawSelf */
xinc = -xinc;
[self resetXinc:xinc];
NXBeep();
beepError = 3;
}
if (xmax <= xmin) {
[self niceMinMaxInc];
NXBeep(); /* alert */
beepError = 4;
}
}
/* And similarly for yinc */
if (yinc*(ymax-ymin) <= 0.0) { /* the bad case - avoid infinite loop */
if (yinc < 0.0) {
yinc = -yinc;
[self resetYinc:yinc];
NXBeep(); /* alert */
beepError = 5;
}
if (ymax <= ymin) {
[self niceMinMaxInc];
NXBeep(); /* alert */
beepError = 6;
}
}
/* Third check: no more than 100 (say) tic marks on either axis */
if ( ![self xaxisLog] ) { /* linear axis */
nticmarks = (int) ((xmax - xmin) / xinc) ;
if (nticmarks > 100) {
computeNiceLinInc(&xmin, &xmax, &xinc);
[self resetXmin:xmin];
[self resetXmax:xmax];
[self resetXinc:xinc];
NXBeep(); /* alert */
beepError = 7;
}
}
if ( ![self yaxisLog] ) { /* linear axis */
nticmarks = (int) ((ymax - ymin) / yinc) ;
if (nticmarks > 100) {
computeNiceLinInc(&ymin, &ymax, &yinc);
[self resetYmin:ymin];
[self resetYmax:ymax];
[self resetYinc:yinc];
NXBeep(); /* alert */
beepError = 8;
}
}
return self;
}
- drawPlot:sender
{
char c_xmin[20], c_xmax[20], c_ymin[20], c_ymax[20], c_xinc[20], c_yinc[20];
float xmin, xmax, ymin, ymax, xinc, yinc;
if (nfilestotal == 0) return self;
[self drawPlotButton:1]; /* display "Plotting" */
[self sanityCheck]; /* disallow various bad parameters */
/* maybe save min/max/inc */
/* This really gets crufty: when we look at the TextField xMin and
* get its float value via [xLimits floatValueAt:0], we get
* a float which is the result of applying atof() to a string. The
* float which results may not be the same as the float that was originally
* written into the TextField. So we apply the process ourselves:
* float --> string (via sprintf) --> float (via atof). Ugly, but necessary
* (otherwise, if you zoom, then hit the Plot button twice, then hit
* the Previous View button, you might not get back to where you want).
*/
sprintf(c_xmin,"%g",currentMin.x);
sprintf(c_xmax,"%g",currentMax.x);
sprintf(c_xinc,"%g",currentInc.x);
sprintf(c_ymin,"%g",currentMin.y);
sprintf(c_ymax,"%g",currentMax.y);
sprintf(c_yinc,"%g",currentInc.y);
xmin = (float)atof(c_xmin);
xmax = (float)atof(c_xmax);
xinc = (float)atof(c_xinc);
ymin = (float)atof(c_ymin);
ymax = (float)atof(c_ymax);
yinc = (float)atof(c_yinc);
if ( [xLimits floatValueAt:0] != xmin
|| [xLimits floatValueAt:1] != xmax
|| [xLimits floatValueAt:2] != xinc
|| [yLimits floatValueAt:0] != ymin
|| [yLimits floatValueAt:1] != ymax
|| [yLimits floatValueAt:2] != yinc ) {
oldMin.x = currentMin.x;
currentMin.x = [xLimits floatValueAt:0];
oldMax.x = currentMax.x;
currentMax.x = [xLimits floatValueAt:1];
oldInc.x = currentInc.x;
currentInc.x = [xLimits floatValueAt:2];
oldMin.y = currentMin.y;
currentMin.y = [yLimits floatValueAt:0];
oldMax.y = currentMax.y;
currentMax.y = [yLimits floatValueAt:1];
oldInc.y = currentInc.y;
currentInc.y = [yLimits floatValueAt:2];
}
[canvas display];
[self drawPlotButton:0]; /* display "Plot" */
return self;
}
// Allocate enough memory and read the data points
/*
* This code makes the following assumptions:
* 1. Any data on a line following the character "!" is to be discarded.
* 2. We can determine the number of curves by looking at the first
* line of data, which should be of the form
* x y1 y2 ... yn
* (possibly separated by commas, with possible trailing comment).
* 3. Other lines of the file may contain arbitrary text, but contain no
* numerals or periods (these get interpreted as floating point numbers
* when the file is scanned); also, anything after a "!" is discarded.
*
* It is not easy to make a completely general and bullet-proof scanning
* routine. This code is fairly robust and was easy to write.
*/
- (int) readData:(NXStream *)aDataStream :(char *)fname
{
BOOL inword = NO;
char c;
int j, size = ALLOCSIZE;
int tmpint = 0; /* tmpint initialized to avoid compiler warning */
int oldncurves = ncurvestotal;
datahunk *pdh = (void *)NULL;
BOOL noxdata = NO;
float tmpfloat = 0.0; /* initialized to avoid compiler warning */
[self preludeToReading:fname :&pdh]; /* take care of some housekeeping */
/* Figure out the number of curves in the file by reading characters */
/* until a newline is encountered; the number of curves is one less */
/* than the number of words found. */
/* We assume the input file is an ascii file; a compressed file will */
/* have been pumped through zcat and written to a temporary file. */
pdh->ncurves = -1;
top_of_file: ;
while (1) {
c = (char)NXGetc(aDataStream);
if (c == (char)EOF) {
break; /* EOF, break out of while loop */
}
if (c == '\n') {
if (pdh->ncurves == -1) {
goto top_of_file; // ugh, but there may be a blank line
}
else {
break; /* we have found some data, break from while loop */
}
}
if (c == '!') { /* comment signal: start discarding characters */
while ( (c=(char)NXGetc(aDataStream)) != '\n' ) ; /* do nothing */
if (pdh->ncurves == -1) /* any data found yet? */
goto top_of_file; /* ugh */
else
break;
}
else if ((inword==NO) && !(c==' ' || c=='\t')) {
pdh->ncurves++;
inword = YES;
}
else if ((inword==YES) && (c==' ' || c=='\t')) {
inword = NO;
}
}
if (pdh->ncurves == -1) { /* couldn't find "\n", give up (after cleanup) */
[plotButton setAltTitle:"Plotting"]; /* reset plot button */
[plotButton highlight:NO];
NXPing(); /* force redraw */
free( (void *)(pdh->filename) );
nfilestotal--;
return 0;
}
if (pdh->ncurves == 0) { /* only one column, assume x data are integers */
noxdata = YES;
tmpfloat = 1.0;
pdh->ncurves = 1;
if (pdh->has_exbars || pdh->has_eybars) {
NXRunAlertPanel("Read Data (with error bars)",
"Error bars expected, only one curve found\n"
"Unsetting error bar button and continuing",
"OK", NULL, NULL);
[errorBars setTitle:"No error bars"];
pdh->has_exbars = NO;
pdh->has_eybars = NO;
}
}
/*
* We read more than one column; if there are error bars we must adjust ncurves.
* case pdh->ncurves true no. of curves
* y only 2n n
* x only n (>=2) n-1
* y and x 2n+1 (>=3) n
*/
if (pdh->has_eybars && !pdh->has_exbars) {
if ( pdh->ncurves % 2 != 0 ) {
NXRunAlertPanel("Read Data (with error bars)",
"Strange number of curves found\n"
"Unsetting error bar button and continuing",
"OK", NULL, NULL);
[errorBars setTitle:"No error bars"];
pdh->has_eybars = NO;
}
else {
pdh->ncurves = pdh->ncurves / 2;
}
}
else if (pdh->has_exbars && !pdh->has_eybars) {
if ( pdh->ncurves < 2 ) {
NXRunAlertPanel("Read Data (with error bars)",
"Too few curves found\n"
"Unsetting error bar button and continuing",
"OK", NULL, NULL);
[errorBars setTitle:"No error bars"];
pdh->has_exbars = NO;
}
else {
pdh->ncurves--;
}
}
else if (pdh->has_exbars && pdh->has_eybars) {
if ( pdh->ncurves < 3 || pdh->ncurves % 2 == 0 ) {
NXRunAlertPanel("Read Data (with error bars)",
"Bad number of curves found\n"
"Unsetting error bar button and continuing",
"OK", NULL, NULL);
[errorBars setTitle:"No error bars"];
pdh->has_exbars = NO;
pdh->has_eybars = NO;
}
else {
pdh->ncurves = (pdh->ncurves - 1) / 2;
}
}
/* Now read the data into memory */
NXSeek(aDataStream, 0L, NX_FROMSTART);
pdh->x = (NXCoord *)malloc( size * sizeof(NXCoord) );
if (pdh->has_exbars) {
pdh->ex = (NXCoord *)malloc( size * sizeof(NXCoord) );
}
pdh->y = (NXCoord **)malloc( pdh->ncurves * sizeof(NXCoord *) );
for (j = 0; j < pdh->ncurves; j++) {
*(pdh->y+j) = (NXCoord *)malloc( size * sizeof(NXCoord) );
}
if (pdh->has_eybars) {
pdh->ey = (NXCoord **)malloc( pdh->ncurves * sizeof(NXCoord *) );
for (j = 0; j < pdh->ncurves; j++) {
*(pdh->ey+j) = (NXCoord *)malloc( size * sizeof(NXCoord) );
}
}
pdh->npoints = 0;
while(1) {
if (noxdata) {
*(pdh->x+pdh->npoints) = tmpfloat++;
}
else {
if (pdh->has_exbars) { /* x error bars, read two items*/
while ( (tmpint=NXScanf(aDataStream, "%f", pdh->x+pdh->npoints)) == 0 ) {
if (c == '!') {
while ( (c=(char)NXGetc(aDataStream)) != '\n' ) ; /* do nothing */
goto skipline; /* ugh */
}
}
while ( (tmpint=NXScanf(aDataStream, "%f", pdh->ex+pdh->npoints)) == 0 ) {
if (c == '!') {
while ( (c=(char)NXGetc(aDataStream)) != '\n' ) ; /* do nothing */
goto skipline; /* ugh */
}
}
}
else { /* no x error bars, read one item */
while ( (tmpint=NXScanf(aDataStream, "%f", pdh->x+pdh->npoints)) == 0 ) {
c = (char)NXGetc(aDataStream); /* throw away extraneous characters */
if (c == '!') {
while ( (c=(char)NXGetc(aDataStream)) != '\n' ) ; /* do nothing */
goto skipline; /* ugh */
}
}
}
if (tmpint == EOF) break; /* break out of the while(1) loop */
}
if (pdh->has_eybars) { /* y error bars, read two items */
for (j = 0; j < 2 * pdh->ncurves; j++) {
if (j%2 == 0) { /* j is even, reading y value */
while( (tmpint=NXScanf(aDataStream, "%f", *(pdh->y+(j/2))+ pdh->npoints)) == 0 ) {
c = (char)NXGetc(aDataStream); /* throw away the next character */
if (c == '!') { /* comment signal; start discarding characters */
while ( (c=(char)NXGetc(aDataStream)) != '\n' ) ; /* do nothing */
goto skipline; /* ugh */
}
}
}
else { /* j is odd, reading error */
while( (tmpint=NXScanf(aDataStream, "%f", *(pdh->ey+(j-1)/2)+ pdh->npoints)) == 0 ) {
c = (char)NXGetc(aDataStream);
if (c == '!') {
while ( (c=(char)NXGetc(aDataStream)) != '\n' ) ;
goto skipline;
}
}
}
}
}
else { /* no error bars, read one item */
for (j = 0; j < pdh->ncurves; j++) {
/*
* Try to allow extraneous characters here (if scanf returns 0, which means
* it didn't find a number, just do a getc on the input stream to throw that
* character away. This will allow commas and alphabetic characters in the
* middle of an input line (no digits or periods, though!).
*/
while( (tmpint=NXScanf(aDataStream, "%f", *(pdh->y+j)+ pdh->npoints)) == 0 ) {
c = (char)NXGetc(aDataStream); /* throw away the next character */
if (c == '!') { /* comment signal; start discarding characters */
while ( (c=(char)NXGetc(aDataStream)) != '\n' ) ; /* do nothing */
goto skipline; /* ugh */
}
}
}
}
if (tmpint == EOF) break; /* could get this if noxdata==YES */
pdh->npoints++;
if (pdh->npoints == size) { /* get more memory */
size += ALLOCSIZE;
pdh->x = (NXCoord *)realloc(pdh->x, size * sizeof(NXCoord));
if (pdh->has_exbars) {
pdh->ex = (NXCoord *) realloc(pdh->ex, size * sizeof(NXCoord));
}
for (j = 0; j < pdh->ncurves; j++) {
*(pdh->y+j) = (NXCoord *)realloc( *(pdh->y+j), size * sizeof(NXCoord) );
}
if (pdh->has_eybars) {
for (j = 0; j < pdh->ncurves; j++) {
*(pdh->ey+j) = (NXCoord *)realloc( *(pdh->ey+j), size * sizeof(NXCoord) );
}
}
}
skipline: ;
}
[self postludeToReading:fname :oldncurves :pdh];
ncurvestotal += pdh->ncurves;
return pdh->npoints;
}
/* Might want to make sure INLINE_MATH is defined when math.h is included
* (for guaranteed fast logarithms?)
*/
- checkLinLog:(datahunk *)pdh
{
/* Check x and y axes -- do a heuristic test for log/lin.
* The test employed comes from M. Merriam and xyplot.
*/
double scale, linsum, logsum;
register double tmp;
int i, j;
/* First test x axis. */
if (pdh->datamax.x > 0.0 && pdh->datamin.x > 0.0
&& pdh->datamax.x != pdh->datamin.x) {
scale = fabs( (double)pdh->datamax.x - (double)pdh->datamin.x );
linsum = 0.0;
for (j=1; j<pdh->npoints; j++) {
tmp = ( (double)pdh->x[j]-(double)pdh->x[j-1] )/(double)scale;
linsum += tmp*tmp;
}
scale = log10( (double)pdh->datamax.x/(double)pdh->datamin.x );
/* what if datamax.x<datamin.x? */
logsum = 0.0;
for (i=1; i<pdh->npoints; i++) {
tmp = log10( (double)pdh->x[i]/(double)pdh->x[i-1] ) / scale;
logsum += tmp*tmp;
}
if (linsum < logsum) {
pdh->xaxislin = YES; /* linear axis */
}
else {
pdh->xaxislin = NO; /* logarithmic axis */
}
}
else {
pdh->xaxislin = YES; /* linear */
}
/* Now test y axis */
if (pdh->datamax.y > 0.0 && pdh->datamin.y > 0.0
&& pdh->datamax.y != pdh->datamin.y) {
scale = fabs( (double)pdh->datamax.y - (double)pdh->datamin.y );
linsum = 0.0;
for (j=0; j<pdh->ncurves; j++) {
for (i=1; i<pdh->npoints; i++) {
tmp = ( (double)*(*(pdh->y+j)+i) - (double)*(*(pdh->y+j)+i-1) )
/ scale; /* avoid overflow */
linsum += tmp*tmp;
}
}
scale = log10((double)pdh->datamax.y/(double)pdh->datamin.y);
logsum = 0.0;
for (j=0; j<pdh->ncurves; j++) {
for (i=1; i<pdh->npoints;i++) {
tmp = log10( (double)*(*(pdh->y+j)+i)/(double)*(*(pdh->y+j)+i-1) ) / scale;
logsum += tmp*tmp;
}
}
if (linsum < logsum) {
pdh->yaxislin = YES; /* linear axis */
}
else {
pdh->yaxislin = NO; /* logarithmic axis */
}
}
else {
pdh->yaxislin = YES; /* linear */
}
return self;
}
// This routine works as follows:
// If these is just one file, we set the x and y axes to linear or logarithmic
// depending on our heuristic.
// If there is more than one file, we don't change the axes unless there
// would be an illegal result (trying to plot a negative number on a
// logarithmic axis).
- checkGlobalLinLog
{
datahunk *pdh;
if (nfilestotal == 1) {
pdh = (datahunk *)[datahunkArray elementAt:0];
if ( pdh->xaxislin )
[xLinLog setState:0]; /* linear */
else
[xLinLog setState:1]; /* logarithmic */
if ( pdh->yaxislin )
[yLinLog setState:0]; /* linear */
else
[yLinLog setState:1]; /* logarithmic */
}
else {
if ( [self xaxisLog] && globaldatamin.x <= 0.0) {
[xLinLog setState:0]; /* back to linear */
NXBeep(); /* audible alert */
beepError = 9;
}
if ( [self yaxisLog] && globaldatamin.y <= 0.0) {
[yLinLog setState:0]; /* back to linear */
NXBeep(); /* audible alert */
beepError = 10;
}
}
[xLinLog display];
[yLinLog display];
return self;
}
- adjustLineStyleMatrix:(int)column :(int)row
{
/* Adjust the given column of the lineMatrix object, turning off all
* buttons except for the given row.
*/
int i;
for (i = 0; i < N_LINE_STYLES; i++) {
[ [lineMatrix cellAt:i :column] setState:0];
}
[ [lineMatrix cellAt:row :column] setState:1];
return self;
}
- redisplayLineStyleMatrix
{
[lineMatrix display];
return self;
}
- adjustSymbolTypeMatrix:(int)column :(int)row
{
/* Adjust the given column of the symbolMatrix object, turning off all
* buttons except for the given row.
*/
int i;
for (i = 0; i < N_SYMBOL_STYLES; i++) {
[ [symbolMatrix cellAt:i :column] setState:0];
}
[ [symbolMatrix cellAt:row :column] setState:1];
return self;
}
- redisplaySymbolTypeMatrix
{
[symbolMatrix display];
return self;
}
- adjustPanels:(int)oldn :(int)newn
{
/*
* Resize the linestyle matrix, the symbolstyle matrix, and
* the legend form.
* Also arrange to select and highlight only the top button in
* each column of the linestyle and symbolstyle matrices.
* This code could stand to be cleaned up.
*/
char formtitle[80];
int j, k;
datahunk *pdh;
if (newn == -1) { /* flag to delete all and start over */
for (j=oldn-1; j>=1; j--) {
[lineText removeColAt:j andFree:YES];
[symbolText removeColAt:j andFree:YES];
[lineMatrix removeColAt:j andFree:YES];
[symbolMatrix removeColAt:j andFree:YES];
[legendForm removeEntryAt:j];
}
for (j=0; j<1; j++) {
if ( [ [lineMatrix cellAt:0 :j] state ] != 1 ) /* seems necessary */
[ [lineMatrix cellAt:0 :j] setState:1];
if ( [ [symbolMatrix cellAt:0 :j] state ] != 1 ) /* seems necessary */
[ [symbolMatrix cellAt:0 :j] setState:1];
for (k=1; k<N_LINE_STYLES; k++) {
[ [lineMatrix cellAt:k :j] setState:0];
}
for (k=1; k<N_SYMBOL_STYLES; k++) {
[ [symbolMatrix cellAt:k :j] setState:0];
}
sprintf(formtitle, "Curve %d", j+1);
[legendForm setStringValue:formtitle at:j];
[legendForm drawCellAt:j];
sprintf(formtitle, "%d", j+1);
[[lineText cellAt:0 :j] setStringValue:formtitle]; /* titles on columns */
[[symbolText cellAt:0 :j] setStringValue:formtitle]; /* titles on columns */
}
// [legendTitle setStringValue:"Legend" at:0]; should we uncomment this?
// (would need to add an outlet in IB)
[lineText sizeToCells];
[symbolText sizeToCells];
[lineMatrix sizeToCells];
[symbolMatrix sizeToCells];
[legendForm sizeToFit];
[ [lineMatrix window] display]; /* redraw the whole window so extra */
[ [symbolMatrix window] display]; /* columns get erased */
[ [legendForm window] display];
[curveNumber setIntValue:1]; /* update curve no. on color panel */
[curveColorWell setColor:NX_COLORBLACK]; /* and update the color */
/* (the background and text color wells are not reset) */
}
if ( (oldn == 0) && (newn > 1) ) { /* can only happen first time */
pdh = [datahunkArray elementAt:0];
for (j=1; j<newn; j++) {
[lineText addCol];
[symbolText addCol];
[lineMatrix addCol];
if ( [ [lineMatrix cellAt:0 :j] state ] != 1 ) /* seems necessary */
[ [lineMatrix cellAt:0 :j] setState:1];
/* highlight 1st row new column -- this occurs automatically */
[symbolMatrix addCol];
if ( [ [symbolMatrix cellAt:0 :j] state ] != 1 ) /* seems necessary */
[ [symbolMatrix cellAt:0 :j] setState:1];
/* highlighting of 1st row new column occurs automatically */
sprintf(formtitle, "Curve %d:", j+1);
[legendForm addEntry:formtitle];
sprintf(formtitle, "%s, Curve %d",
strrchr(pdh->filename,'/')==NULL ? pdh->filename
: strrchr(pdh->filename,'/') + 1,
j+1);
[legendForm setStringValue:formtitle at:j];
[legendForm drawCellAt:j];
sprintf(formtitle, "%d", j+1);
[[lineText cellAt:0 :j] setStringValue:formtitle]; /* titles on columns */
[[symbolText cellAt:0 :j] setStringValue:formtitle]; /* titles on columns */
}
sprintf(formtitle, "%s, Curve 1",
strrchr(pdh->filename,'/')==NULL ? pdh->filename
: strrchr(pdh->filename,'/') + 1);
[legendForm setStringValue:formtitle at:0];
[legendForm drawCellAt:0];
[lineText sizeToCells];
[symbolText sizeToCells];
[lineMatrix sizeToCells];
[symbolMatrix sizeToCells];
[legendForm sizeToFit];
[lineText display];
[symbolText display];
[lineMatrix display]; /* don't need to redraw the window here */
[symbolMatrix display];
[legendForm display];
}
if ( (oldn == 0) && (newn == 1) ) { /* only for legendForm update */
pdh = [datahunkArray elementAt:0];
sprintf(formtitle, "%s, Curve 1",
strrchr(pdh->filename,'/')==NULL ? pdh->filename
: strrchr(pdh->filename,'/') + 1);
[legendForm setStringValue:formtitle at:0];
[legendForm drawCellAt:0];
[legendForm display];
}
if ( oldn != 0 ) { /* must add columns */
pdh = [datahunkArray elementAt:(nfilestotal-1)];
for (j=oldn; j<oldn+newn; j++) {
[lineText addCol];
[symbolText addCol];
[lineMatrix addCol];
[ [lineMatrix cellAt:0 :j] setState:1];
/* highlighting of 1st row new column occurs automatically */
[symbolMatrix addCol];
[ [symbolMatrix cellAt:0 :j] setState:1];
/* highlighting of 1st row new column occurs automatically */
sprintf(formtitle, "Curve %d:", j+1);
[legendForm addEntry:formtitle];
sprintf(formtitle, "%s, Curve %d",
strrchr(pdh->filename,'/')==NULL ? pdh->filename
: strrchr(pdh->filename,'/') + 1,
j+1-oldn);
[legendForm setStringValue:formtitle at:j];
[legendForm drawCellAt:j];
sprintf(formtitle, "%d", j+1);
[[lineText cellAt:0 :j] setStringValue:formtitle];
[[symbolText cellAt:0 :j] setStringValue:formtitle];
}
[lineText sizeToCells];
[symbolText sizeToCells];
[lineMatrix sizeToCells];
[symbolMatrix sizeToCells];
[legendForm sizeToFit];
[lineText display];
[symbolText display];
[lineMatrix display]; /* don't need to redraw the window here */
[symbolMatrix display];
[legendForm display];
}
[self adjustScrollWindows];
return self;
}
- adjustScrollWindows
{
// code from pdhowell for ScrollWindowing
NXRect legendFormFrame, lineMatrixFrame, symbolMatrixFrame;
[legendForm getFrame:&legendFormFrame];
[lineMatrix getFrame:&lineMatrixFrame];
[symbolMatrix getFrame:&symbolMatrixFrame];
// Put MAX in the the following lines because we don't want to allow the windows
// to get so small that the sliders aren't drawn; the 282 and 288 are the original
// sizes of the lineMatrixWindow and symbolMatrixWindow -- dcj
[[[lineMatrixWindow contentView] docView]
sizeTo:MAX(128.0+lineMatrixFrame.size.width,282.0)
:92+lineMatrixFrame.size.height];
[[[symbolMatrixWindow contentView] docView]
sizeTo:MAX(128.0+symbolMatrixFrame.size.width,288.0)
:92+symbolMatrixFrame.size.height];
[[[legendFormWindow contentView] docView]
sizeTo:32+legendFormFrame.size.width
:192+legendFormFrame.size.height];
// Try sending windowDidResize (the "self" is bogus but does no harm) -- dcj
[legendFormWindow windowDidResize:self];
[lineMatrixWindow windowDidResize:self];
[symbolMatrixWindow windowDidResize:self];
// Try to get the windows to display properly ... dcj
[ [lineMatrix window] display];
[ [symbolMatrix window] display];
[ [legendForm window] display];
return self;
}
// Go through a particular datahunk and find values for datamin.x,
// datamax.x, datamin.y, datamax.y
// We ignore any curves that are "turned off" (linestyle=NONE & symbolstyle=NONE);
// if all curves in the datahunk are turned off we ignore the x-data, too.
- findMinMax:(datahunk *)pdh
{
int i, j;
datahunk *qdh;
int n, ncurve;
BOOL ignore;
pdh->datamin.x = MAXFLOAT;
pdh->datamax.x = -MAXFLOAT;
pdh->datamin.y = MAXFLOAT;
pdh->datamax.y = -MAXFLOAT;
// Find out which curves belong to datahunk pdh
ncurve = 0;
for (n=0; n<nfilestotal; n++) {
qdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
if (qdh == pdh)
break;
else
ncurve += qdh->ncurves;
}
// Now we know to look at curves ncurve, ncurve+1,...,ncurve+pdh->ncurves-1
ignore = YES;
for (n = ncurve; n < ncurve + pdh->ncurves; n++) {
if ([self providelinestyle:n] != NOLINE
|| [self providesymbolstyle:n] != NOSYMBOL)
ignore = NO;
}
// Now it's possible we want to ignore all the curves.
if (ignore) return self;
// If we don't ignore all the curves then we must look at the x data.
for (i = 0; i < pdh->npoints; i++) {
pdh->datamin.x = MIN(pdh->datamin.x, pdh->x[i]);
pdh->datamax.x = MAX(pdh->datamax.x, pdh->x[i]);
}
for (j = 0; j < pdh->ncurves; j++) {
if ([self providelinestyle:(ncurve+j)] != NOLINE
|| [self providesymbolstyle:(ncurve+j)] != NOSYMBOL) {
for (i = 0; i < pdh->npoints; i++) {
pdh->datamin.y = MIN(pdh->datamin.y, *(*(pdh->y+j)+i));
pdh->datamax.y = MAX(pdh->datamax.y, *(*(pdh->y+j)+i));
}
}
}
return self;
}
- findGlobalMinMax
{
int n;
datahunk *pdh;
globaldatamin.x = MAXFLOAT;
globaldatamin.y = MAXFLOAT;
globaldatamax.x = -MAXFLOAT;
globaldatamax.y = -MAXFLOAT;
for (n=0; n<nfilestotal; n++) {
pdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
globaldatamin.x = MIN(globaldatamin.x, pdh->datamin.x);
globaldatamax.x = MAX(globaldatamax.x, pdh->datamax.x);
globaldatamin.y = MIN(globaldatamin.y, pdh->datamin.y);
globaldatamax.y = MAX(globaldatamax.y, pdh->datamax.y);
}
return self;
}
// Get pleasing values for the min, max, and increments
- niceMinMaxInc
{
float fmin, fmax, finc;
fmin = globaldatamin.x;
fmax = globaldatamax.x;
if ([self xaxisLog] ) {
computeNiceLogInc(&fmin, &fmax, &finc);
}
else {
computeNiceLinInc(&fmin, &fmax, &finc);
}
[self resetXmin:fmin];
[self resetXmax:fmax];
[self resetXinc:finc];
fmin = globaldatamin.y;
fmax = globaldatamax.y;
if ([self yaxisLog] ) {
computeNiceLogInc(&fmin, &fmax, &finc);
}
else {
computeNiceLinInc(&fmin, &fmax, &finc);
}
[self resetYmin:fmin];
[self resetYmax:fmax];
[self resetYinc:finc];
return self;
}
// Use the OpenPanel object to get a filename
- open:sender
{
static const char *const fileTypes[2] = {NULL, NULL};
/* this is supposedly all ASCII files */
char fname[256];
id openPanel = [[OpenPanel new] allowMultipleFiles:NO];
[openPanel setAccessoryView:nil]; /* may have to clean out an accessory view */
if (nfilestotal == 0) {
if (strncmp([errorBars title], "No error bars", 13) != 0) {
[openPanel setTitle:"Open (error bars)"];
}
else {
[openPanel setTitle:"Open"]; /* make sure title is OK (cf. binary open) */
}
}
else {
if (strncmp([errorBars title], "No error bars", 13) != 0) {
[openPanel setTitle:"Another (error bars)"]; /* 21 character limit here? */
}
else {
[openPanel setTitle:"Open Additional File"];
}
}
if ([openPanel runModalForTypes:fileTypes]) {
strncpy(fname, (char *)[openPanel filename], 256);
// Check to see if we are trying to open a compressed file
// We check for both ".Z" and ".gz"; we are assuming that the
// decompression command zcat will handle both of these cases
if (fname[strlen(fname)-1]=='Z' && fname[strlen(fname)-2]=='.') {
[self handleCompressedFile:fname];
}
else {
[self openFile:fname :fname];
}
}
return self;
}
- handleCompressedFile:(char *)fname
{
char tempfname[256], command[512];
// set plot button title:
[plotButton setAltTitle:"Uncompressing"];
[plotButton highlight:YES];
NXPing(); /* force plotButton redraw */
// Uncompress the file into a temporary file:
strcpy(tempfname, "/tmp/file000000.xyp");
NXGetTempFilename(tempfname, 9);
sprintf(command, "zcat %s > %s\n", fname, tempfname);
// Possible danger ahead: no error checking; what if, e.g, /tmp is full?
system(command);
// Now just go ahead and open the temporary file:
[self openFile:tempfname :fname];
// After returning from the openFile it is safe to unlink:
unlink(tempfname);
return self;
}
- openFile:(char *)dataFile :(char *)realName
{
NXStream *dataStream;
if ((dataStream = NXMapFile(dataFile, NX_READONLY)) == NULL) {
NXRunAlertPanel("Open", "Cannot open %s", "OK", NULL, NULL, dataFile);
return self;
}
if ([self readData:dataStream :realName] == 0) {
NXRunAlertPanel("Read", "Couldn't read any data from %s", "OK",
NULL, NULL, dataFile);
NXCloseMemory(dataStream, NX_FREEBUFFER);
return self;
}
NXCloseMemory(dataStream, NX_FREEBUFFER);
[self plotPrepAndDraw];
return self;
}
- openBinary:sender
{
static const char *const fileTypes[2] = {NULL, NULL};
char fname[256];
id openPanel = [[OpenPanel new] allowMultipleFiles:NO];
[openPanel setAccessoryView:binaryOpenAccessory];
if (strncmp([errorBars title], "No error bars", 13) != 0) {
[openPanel setTitle:"Open Binary (error bars)"];
}
else {
[openPanel setTitle:"Open Binary File"];
}
if ([openPanel runModalForTypes:fileTypes]) {
if ([binaryOpenForm intValueAt:0] < 1) {
NXRunAlertPanel("Binary Read",
"Number of curves is less than 1\n"
"Be sure to set this correctly",
"OK", NULL, NULL);
return self;
}
strncpy(fname, (char *)[openPanel filename], 256);
[self openBinaryFile:fname];
}
return self;
}
#import <sys/stat.h>
- openBinaryFile:(char *)dataFile
{
struct stat filestat;
int filesize, numpoints;
int numcurves = [binaryOpenForm intValueAt:0];
BOOL xdatathere = [binaryXdatathere state];
NXStream *dataStream;
int numcols; /* number of "columns" expected in the data file */
if (strncmp([errorBars title], "No error bars", 13) == 0) {
numcols = numcurves; /* no error bars */
}
else if (strncmp([errorBars title], "y only", 6) == 0) {
numcols = 2*numcurves;
}
else if (strncmp([errorBars title], "x only", 6) == 0) {
numcols = numcurves + 1;
}
else {
numcols = 2*numcurves + 1;
}
stat(dataFile, &filestat);
filesize = filestat.st_size;
if (xdatathere) {
// This consistency check is not sufficient to guarantee numcurves is
// correct, but it's better than nothing.
if ( (filesize % (sizeof(NXCoord)*(numcols+1))) != 0 ) {
NXRunAlertPanel("Binary Read",
"File size inconsistent with number\n"
"of curves specified",
"OK", NULL, NULL);
return self;
}
numpoints = filesize/(sizeof(NXCoord)*(numcols+1));
}
else {
if ( (filesize % (sizeof(NXCoord)*numcols)) != 0 ) {
NXRunAlertPanel("Binary Read",
"File size inconsistent with number\n"
"of curves specified",
"OK", NULL, NULL);
return self;
}
numpoints = filesize/(sizeof(NXCoord)*(numcols));
}
if ((dataStream = NXMapFile(dataFile, NX_READONLY)) == NULL) {
NXRunAlertPanel("Open", "Cannot open %s", "OK", NULL, NULL, dataFile);
return self;
}
if ([self readBinaryData:dataStream :dataFile :numcurves
:numpoints :xdatathere] == 0) {
NXRunAlertPanel("Read", "Couldn't read any data from %s", "OK",
NULL, NULL, dataFile);
NXCloseMemory(dataStream, NX_FREEBUFFER);
return self;
}
NXCloseMemory(dataStream, NX_FREEBUFFER);
[self plotPrepAndDraw];
return self;
}
- (int)readBinaryData:(NXStream *)aDataStream
:(char *)fname
:(int)numcurves
:(int)numpoints
:(BOOL)xdatathere
{
int j, oldncurves = ncurvestotal;
datahunk *pdh = (void *)NULL;
[self preludeToReading:fname :&pdh]; /* take care of some housekeeping */
pdh->ncurves = numcurves;
/* Now read the data into memory */
NXSeek(aDataStream, 0L, NX_FROMSTART);
pdh->x = (NXCoord *)malloc( numpoints * sizeof(NXCoord) );
if (pdh->has_exbars) {
pdh->ex = (NXCoord *)malloc( numpoints * sizeof(NXCoord *) );
}
pdh->y = (NXCoord **)malloc( pdh->ncurves * sizeof(NXCoord *) );
for (j = 0; j < pdh->ncurves; j++) {
*(pdh->y+j) = (NXCoord *)malloc( numpoints * sizeof(NXCoord) );
}
if (pdh->has_eybars) {
pdh->ey = (NXCoord **)malloc( pdh->ncurves * sizeof(NXCoord *) );
for (j = 0; j < pdh->ncurves; j++) {
*(pdh->ey+j) = (NXCoord *)malloc( numpoints * sizeof(NXCoord) );
}
}
pdh->npoints = numpoints;
if (xdatathere) {
NXRead(aDataStream, pdh->x, numpoints*sizeof(NXCoord));
}
else {
for (j=0; j < numpoints; j++) {
pdh->x[j] = (float)j;
}
}
if (pdh->has_exbars) {
NXRead(aDataStream, pdh->ex, numpoints*sizeof(NXCoord));
}
for (j = 0; j < pdh->ncurves; j++) {
NXRead(aDataStream, *(pdh->y+j), numpoints*sizeof(NXCoord));
if (pdh->has_eybars) {
NXRead(aDataStream, *(pdh->ey+j), numpoints*sizeof(NXCoord));
}
}
[self postludeToReading:fname :oldncurves :pdh];
ncurvestotal += pdh->ncurves;
return pdh->npoints;
}
- postludeToReading:(char *)fname :(int)oldncurves :(datahunk *)pdh
{
int j;
/* Adjust the lineMatrix, symbolMatrix, and legendForm */
[self adjustPanels:oldncurves :pdh->ncurves];
if ([columnPanel isVisible])
[columnSelectionHandler fixPanel:self];
/*
* Error bars don't get drawn unless the error bar matrix is correct.
* Therefore we don't test on visibility here, rather we test on pdh itself.
*/
if (pdh->has_exbars || pdh->has_eybars)
[errorBarHandler updatePanel:self];
if ([fileRemovalPanel isVisible])
[self fixFileRemovalPanel:self];
/* reset plot button */
[plotButton setAltTitle:"Plotting"];
[plotButton highlight:NO];
[self findMinMax:pdh];
/*
* Don't bother to check lin/log unless this is the first file or
* we already have at least one logarithmic axis:
*/
if (nfilestotal==1 || [self xaxisLog] || [self yaxisLog] ) {
[self checkLinLog:pdh];
}
else {
pdh->xaxislin = YES;
pdh->yaxislin = YES;
}
curvecolors = (NXColor *)realloc((void *)curvecolors,
(ncurvestotal + pdh->ncurves) * sizeof(NXColor));
[[canvas window] setTitleAsFilename:fname];
if (!colorOption) {
for (j=0; j<pdh->ncurves; j++) {
curvecolors[j + ncurvestotal] = NX_COLORBLACK;
}
}
else {
[self makeCurvesColorful:pdh]; /* adjust all the curve colors */
}
return self;
}
- preludeToReading:(char *)fname :(datahunk **)pdh
{
datahunk dh;
/* set plot button title "Reading" */
[plotButton setAltTitle:"Reading"];
[plotButton highlight:YES];
NXPing(); /* force plotButton redraw */
if (nfilestotal == 0) {
datahunkArray = [Storage newCount:1
elementSize:sizeof(datahunk)
description:"{*{float *}{float *}{float **}{float **}iiffff{BOOL}{BOOL}{BOOL}{BOOL}}"];
*pdh = (datahunk *)[datahunkArray elementAt:0];
if (*pdh == NULL) {
NXRunAlertPanel("readData",
"Weird error 0: NULL pointer in readData\n"
"I can't continue",
"OK", NULL, NULL);
exit(0);
}
nfilestotal = 1;
}
else {
[datahunkArray addElement:(void *)&dh];
*pdh = (datahunk *)[datahunkArray elementAt:(unsigned)nfilestotal];
if (*pdh == NULL) {
NXRunAlertPanel("readData",
"Weird error 1: NULL pointer in readData\n"
"I can't continue",
"OK", NULL, NULL);
exit(0);
}
nfilestotal++;
}
(*pdh)->filename = (char *)malloc(strlen(fname) + 1);
strncpy((*pdh)->filename, fname, strlen(fname) + 1);
(*pdh)->has_exbars = NO;
(*pdh)->has_eybars = NO;
if (strncmp([errorBars title], "y only", 6) == 0) {
(*pdh)->has_eybars = YES;
}
else if (strncmp([errorBars title], "x only", 6) == 0) {
(*pdh)->has_exbars = YES;
}
else if (strncmp([errorBars title], "x and y", 7) == 0) {
(*pdh)->has_exbars = YES;
(*pdh)->has_eybars = YES;
}
return self;
}
- plotPrepAndDraw
{
/* Check for linear or log on x and y axes */
[self checkGlobalLinLog];
[self findGlobalMinMax];
/* Only check and reset min/max if this is the first file */
if (nfilestotal == 1) {
[self niceMinMaxInc];
}
if ([autoPlotButton state] == 1) {
return self;
}
else {
[self drawPlot:self];
}
return self;
}
- writeDataFiles:sender
{
int i, j, n;
datahunk *pdh;
NXStream *outputStream;
char filename[1024], paneltitle[256];
id savePanel = [[SavePanel new] setRequiredFileType:""];
if (nfilestotal == 0) {
NXRunAlertPanel("Write Data", "No data", "OK", NULL, NULL);
}
else {
for (n=0; n<nfilestotal; n++) {
pdh = (datahunk *)[datahunkArray elementAt:(unsigned)n];
sprintf(paneltitle, "Save file %d (%s)", n+1,
strrchr(pdh->filename,'/')==NULL ? pdh->filename
: strrchr(pdh->filename,'/')+1);
[savePanel setTitle:paneltitle];
[savePanel setAccessoryView:writeDataAccButton];
if ([savePanel runModal]) {
strncpy(filename, [savePanel filename], 1024);
if ((outputStream = NXOpenMemory(NULL, 0, NX_WRITEONLY)) == NULL) {
NXRunAlertPanel("Write Data", "Cannot open memory for file %d",
"OK", NULL, NULL, n);
return self;
}
if ([writeDataAccButton state]) { /* ascii write */
for (i=0; i<pdh->npoints; i++) {
NXPrintf(outputStream, "%g", pdh->x[i]);
if (pdh->has_exbars) {
NXPrintf(outputStream, " %g", pdh->ex[i]);
}
for (j=0; j<pdh->ncurves; j++) {
NXPrintf(outputStream, " %g", *(*(pdh->y+j)+i));
if (pdh->has_eybars) {
NXPrintf(outputStream, " %g", *(*(pdh->ey+j)+i));
}
}
NXPrintf(outputStream, "\n");
}
}
else { /* binary write */
NXWrite(outputStream, pdh->x, (pdh->npoints)*sizeof(NXCoord));
if (pdh->has_exbars) {
NXWrite(outputStream, pdh->ex, (pdh->npoints)*sizeof(NXCoord));
}
for (j=0; j<pdh->ncurves; j++) {
NXWrite(outputStream, *(pdh->y+j), (pdh->npoints)*sizeof(NXCoord));
if (pdh->has_eybars) {
NXWrite(outputStream, *(pdh->ey+j), (pdh->npoints)*sizeof(NXCoord));
}
}
}
NXFlush(outputStream);
NXSaveToFile(outputStream, filename);
NXClose(outputStream);
}
}
}
return self;
}
- whyTheBeep:sender
{
switch(beepError) {
case 0:
NXRunAlertPanel("The Beep Panel", "Press this button if the program beeps\n"
"and you want to know why", "OK", NULL, NULL);
break;
case 1:
NXRunAlertPanel("The Beep Happened Because:",
"x-axis changed from log to linear", "OK", NULL, NULL);
break;
case 2:
NXRunAlertPanel("The Beep Happened Because:",
"y-axis changed from log to linear", "OK", NULL, NULL);
break;
case 3:
NXRunAlertPanel("The Beep Happened Because:",
"x-increment was negative\n (I changed it)", "OK", NULL, NULL);
break;
case 4:
NXRunAlertPanel("The Beep Happened Because:",
"xmax was less than xmin\n (I changed them)", "OK", NULL, NULL);
break;
case 5:
NXRunAlertPanel("The Beep Happened Because:",
"y-increment was negative\n (I changed it)", "OK", NULL, NULL);
break;
case 6:
NXRunAlertPanel("The Beep Happened Because:",
"ymax was less than ymin\n (I changed them)", "OK", NULL, NULL);
break;
case 7:
NXRunAlertPanel("The Beep Happened Because:",
"Too many tic marks would have been on the x-axis\n"
" (I changed the min, max, and/or increment)",
"OK", NULL, NULL);
break;
case 8:
NXRunAlertPanel("The Beep Happened Because:",
"Too many tic marks would have been on the y-axis\n"
" (I changed the min, max, and/or increment)",
"OK", NULL, NULL);
break;
case 9:
NXRunAlertPanel("The Beep Happened Because:",
"x-axis was logarithmic but some datum was negative\n"
" (I changed x-axis to linear)", "OK", NULL, NULL);
break;
case 10:
NXRunAlertPanel("The Beep Happened Because:",
"y-axis was logarithmic but some datum was negative\n"
" (I changed y-axis to linear)", "OK", NULL, NULL);
break;
case 11:
NXRunAlertPanel("The Beep Happened Because:",
"tried to set color of a non-existent curve", "OK", NULL, NULL);
break;
case 12:
NXRunAlertPanel("The Beep Happened Because:",
"an increment was too small for a log axis\n"
" (I reset it)", "OK", NULL, NULL);
break;
}
return self;
}
- (NXColor) provideBackgroundColor
{
if ( ( ([printPreview state] == 1) || (NXDrawingStatus == NX_PRINTING))
&& ([accPrintColorButton state] == 0) ) {
return NX_COLORWHITE;
}
else {
return backgroundcolor;
}
}
- (NXColor) provideTextColor
{
if ( ( ([printPreview state] == 1) || (NXDrawingStatus == NX_PRINTING))
&& ([accPrintColorButton state] == 0) ) {
return NX_COLORBLACK;
}
else {
return textcolor;
}
}
- (NXColor) provideCurveColor:(int)aCurve
{
if ( ( ([printPreview state] == 1) || (NXDrawingStatus == NX_PRINTING))
&& ([accPrintColorButton state] == 0) ) {
return NX_COLORBLACK;
}
else {
return curvecolors[aCurve];
}
}
- setBackgroundColor:sender
{
backgroundcolor = [sender color];
return self;
}
- forceBackgroundColor:(NXColor) aColor
{
backgroundcolor = aColor;
return self;
}
- forceTextColor:(NXColor) aColor
{
textcolor = aColor;
return self;
}
- forceCurveColor:(int)curvenum :(NXColor)aColor
{
if (curvenum >= ncurvestotal) {
NXBeep();
beepError = 11;
}
else {
curvecolors[curvenum] = aColor;
}
return self;
}
- setTextColor:sender
{
textcolor = [sender color];
return self;
}
- setCurveColor:sender
{
int curvenum = [curveNumber intValue];
if (curvenum > ncurvestotal || curvenum < 1) {
NXBeep();
beepError = 11;
return self;
}
/* Change the color in the color well right away: */
[sender setColor:[sender color]];
NXPing(); /* make sure it gets displayed */
curvecolors[curvenum-1] = [sender color];
/* Try to be helpful and increment the curvenumber */
curvenum = (curvenum == ncurvestotal? 1 : curvenum + 1);
/* Slight delay (1/2 second) so the well doesn't change instantly. */
usleep((unsigned)500000);
[curveNumber setIntValue:curvenum];
/* Tell the sender (a color well) to go on the next color: */
[sender setColor:curvecolors[curvenum - 1]];
return self;
}
/* Update the curveColorWell if the curve number is changed */
- textDidEnd:textObject endChar:(unsigned short)whyEnd
{
int curvenum = [curveNumber intValue];
if (curvenum > ncurvestotal || curvenum < 1) {
NXBeep();
beepError = 11;
return self;
}
[curveColorWell setColor:curvecolors[curvenum-1]];
NXPing(); /* make sure it gets displayed */
return self;
}
// Update the global colorOption variable. PlotDelegate sends us
// this message when the color option on the preferences panel is toggled.
// Adjust all colors.
- colorOn:(BOOL)onOff
{
int j;
if (onOff) {
colorOption = YES;
backgroundcolor = NX_COLORBLACK;
textcolor = NX_COLORWHITE;
[self makeCurvesColorful:NULL];
}
else {
colorOption = NO;
backgroundcolor = NX_COLORWHITE;
textcolor = NX_COLORBLACK;
// Now we set all curvecolors to black
for (j=0; j < ncurvestotal; j++) {
curvecolors[j] = NX_COLORBLACK;
}
}
[textColorWell setColor:textcolor];
[backgroundColorWell setColor:backgroundcolor];
[curveColorWell setColor:textcolor];
return self;
}
- fixMatrixColumn:sender /* The sender is a matrix */
{
int row, col, i;
row = [sender selectedRow];
col = [sender selectedCol];
if (sender == lineMatrix) {
[self adjustLineStyleMatrix:col :row];
// Now, instead of redisplaying the whole matrix, just redraw the
// cells in the affected column. It's much faster this way.
for (i=0; i<N_LINE_STYLES; i++)
[lineMatrix drawCellAt:i :col];
}
else if (sender == symbolMatrix) {
[self adjustSymbolTypeMatrix:col :row];
// Comment above applies here, too.
for (i=0; i<N_SYMBOL_STYLES; i++)
[symbolMatrix drawCellAt:i :col];
}
return self;
}
/* change the column which is to be taken as the x data */
- swapColumns:(int)prev_col :(int)col forFileNumber:(int)i
{
datahunk *pdh;
float *tmp;
if (col < 0 || prev_col < 0) /* be very cautious */
return self;
if (col==0 && prev_col==0) /* must avoid this special case */
return self;
pdh = (datahunk *)[datahunkArray elementAt:(unsigned)i];
if (prev_col == 0) { /* was first column, generic x-data */
// tmp = pdh->x; /* why doesn't this order work? */
// pdh->x = *(pdh->y + col-1);
// *(pdh->y + col-1) = tmp;
tmp = *(pdh->y + col-1);
*(pdh->y + col-1) = pdh->x;
pdh->x = tmp;
}
else if (col == 0) { /* revert back to generic x-data */
tmp = *(pdh->y + prev_col - 1);
*(pdh->y + prev_col - 1) = pdh->x;
pdh->x = tmp;
}
else { /* swapping y's only */
// tmp = *(pdh->y + prev_col - 1);
// *(pdh->y + prev_col - 1) = *(pdh->y + col - 1);
// *(pdh->y + col - 1) = tmp;
tmp = *(pdh->y + col - 1);
*(pdh->y + col - 1) = *(pdh->y + prev_col - 1);
*(pdh->y + prev_col - 1) = pdh->x;
pdh->x = tmp;
}
[self findMinMax:pdh]; /* reset these values here */
[self findGlobalMinMax];
return self;
}
/*
* This function is called by the PlotView object during zooming.
*/
- stackOldMinMax:(float)xmin :(float)xmax :(float)ymin :(float)ymax
{
oldMin = currentMin; /* structure assignment */
oldMax = currentMax;
oldInc = currentInc; /* have to deal with the increments, too */
currentMin.x = xmin;
currentMax.x = xmax;
currentMin.y = ymin;
currentMax.y = ymax;
currentInc.x = [xLimits floatValueAt:0];
currentInc.y = [yLimits floatValueAt:0];
return self;
}
/*
* This method is invoked by the "Previous View" button on the
* control panel.
*/
- previousView:sender
{
NXPoint tmp;
[self resetXmin:(double)oldMin.x];
[self resetXmax:(double)oldMax.x];
[self resetXinc:(double)oldInc.x];
[self resetYmin:(double)oldMin.y];
[self resetYmax:(double)oldMax.y];
[self resetYinc:(double)oldInc.y];
tmp = oldMin;
oldMin = currentMin;
currentMin = tmp;
tmp = oldMax;
oldMax = currentMax;
currentMax = tmp;
tmp = oldInc;
oldInc = currentInc;
currentInc = tmp;
[self drawPlotButton:1]; /* display "Plotting" */
[canvas display];
[self drawPlotButton:0]; /* display "Plot" */
return self;
}
// Set the colors on the new curves.
// This might also be called to set _all_ the colors (if the color
// option on the preferences panel has been toggled).
- makeCurvesColorful:(datahunk *)pdh
{
int j;
float hue = 0.0;
#define N_PREDEFINED_COLORS 9
NXColor predefined_colors[] = {NX_COLORRED, NX_COLORGREEN, NX_COLORBLUE,
NX_COLORCYAN, NX_COLORYELLOW, NX_COLORMAGENTA,
NX_COLORORANGE, NX_COLORPURPLE, NX_COLORBROWN};
// Those colors come from /usr/include/appkit/color.h
// for (j=0; j<pdh->ncurves + ncurvestotal; j++) {
// hue = 0.6667 * (float)((j+1) % MIN(11, ncurvestotal + pdh->ncurves))
// / (float)MIN(10, ncurvestotal + pdh->ncurves);
// curvecolors[j] = NXConvertHSBToColor(hue, 1.0, 1.0);
// /* update curve no. on color panel */
// [curveNumber setIntValue:j+1];
// /* and update the color */
// [curveColorWell setColor:curvecolors[j]];
// }
// The preceding has the unfortunate effect of changing colors on an already-plotted
// curve if a new file is read in. This is undesirable. Here is a more
// primitive method. Maybe a method based on (repeatable) random number
// generation would be appropriate.
// for (j=0; j<pdh->ncurves; j++) {
// hue = 0.6667 * (float)((j+ncurvestotal+1) % 6) / 5.0;
// curvecolors[j + ncurvestotal] = NXConvertHSBToColor(hue, 1.0, 1.0);
// /* update curve no. on color panel */
// [curveNumber setIntValue:j+ncurvestotal+1];
// /* and update the color */
// [curveColorWell setColor:curvecolors[j+ncurvestotal]];
// }
// All right, here is the method based on random numbers. Maybe this will be OK.
// The trouble with it is that with srandom(10) [see above -- this was about
// the best] the first two curves are about the same shade of green. Now the
// strategy is to preset the first few curve colors, after that they will be
// random.
if (pdh) {
for (j=0; j<pdh->ncurves; j++) {
if (j + ncurvestotal < N_PREDEFINED_COLORS) {
curvecolors[j + ncurvestotal] = predefined_colors[j + ncurvestotal];
}
else {
hue = 0.8 * ((float)random())/2147483647.0 ; /* 2^31 - 1 */
curvecolors[j + ncurvestotal] = NXConvertHSBToColor(hue, 1.0, 1.0);
}
/* nupdate curve no. on color panel */
[curveNumber setIntValue:j+ncurvestotal+1];
/* and update the color */
[curveColorWell setColor:curvecolors[j+ncurvestotal]];
}
}
else {
// Here we set the colors on _all_ the curves.
for (j=0; j<ncurvestotal; j++) {
if (j < N_PREDEFINED_COLORS) {
curvecolors[j] = predefined_colors[j];
}
else {
hue = 0.8 * ((float)random())/2147483647.0 ; /* 2^31 - 1 */
curvecolors[j] = NXConvertHSBToColor(hue, 1.0, 1.0);
}
}
}
return self;
}
@end